﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Web.Script.Serialization;

namespace CallViaPhone
{
    public class CallViaPhone
    {
        private String[] autodiscoverURLs = new string[4];
        private String sipURI;
        private String password;
        private HttpClient client;
        private String ucwaServiceFQDN;
        private String appGUID;
        private string appEndPoint;
        private string fromPhoneNumber="";
        private string toPhoneNumber;
        
        public CallViaPhone(String sipURI, String password, string fromPhoneNumber, string toPhoneNumber)
        {
            Guid guid = Guid.NewGuid(); 
            appGUID = guid.ToString();

            this.sipURI = sipURI;
            this.password = password;
            if (fromPhoneNumber.Equals("") == false)
            {
                this.fromPhoneNumber = "tel:"+fromPhoneNumber;
            }
            
            this.toPhoneNumber = toPhoneNumber;
            HttpClientHandler handler = new HttpClientHandler();
            client = new HttpClient(handler);
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            createAutodiscoverURL(sipURI);               
        }

       private void createAutodiscoverURL(String sipURI)
        {
            int i = sipURI.IndexOf("@");
            string domain = sipURI.Substring(i+1);

            StringBuilder autodiscoverURLHTTPSInternalSb = new StringBuilder("https://lyncdiscoverinternal.");
            autodiscoverURLs[0] = autodiscoverURLHTTPSInternalSb.Append(domain).ToString();
            
            StringBuilder autodiscoverURLHTTPInternalSb = new StringBuilder("http://lyncdiscoverinternal.");
            autodiscoverURLs[1] = autodiscoverURLHTTPInternalSb.Append(domain).ToString();

            StringBuilder autodiscoverURLHTTPSExternalSb = new StringBuilder("https://lyncdiscover.");
            autodiscoverURLs[2] = autodiscoverURLHTTPSExternalSb.Append(domain).ToString();

            StringBuilder autodiscoverURLHTTPExternallSb = new StringBuilder("http://lyncdiscover.");
            autodiscoverURLs[3] = autodiscoverURLHTTPExternallSb.Append(domain).ToString();
        }   

        public void prepareEndpoint()
        {
            String autodiscoverContent = getAutodiscoverContent();
            String userHRef = getContextHref(autodiscoverContent, "user");
            createUcwaServiceFQDN(userHRef);
            string token = string.Empty;
            string authChallenge = string.Empty;
            string applicationsHRef = string.Empty;

            try
            {
                authChallenge = getUserAuthenticationChallenge(userHRef);
            }
            catch (Exception)
            {
                Console.WriteLine("Error. cannot access url for auth challenge " + userHRef);
            }

            try
            {
                token = getAuthToken(authChallenge);
            }
            catch (Exception)
            {
                Console.WriteLine("Error. Wrong user credentials");
                throw new Exception("Error. Wrong user credentials");
            }

            try
            {
                applicationsHRef = getApplications(userHRef, token);
            }
            catch (Exception)
            {
                Console.WriteLine("Error. Cannot access url with token " + userHRef);
            }
            try
            {
                appEndPoint = createApplicationEndPoint(applicationsHRef);
            }
            catch (Exception e)
            {
                Console.WriteLine("Error. Your Lync service does not provide UCWA! Cannot create application endpoint on " + applicationsHRef);
                Console.WriteLine("Server responded : " + e.Message);
            }
        }

        private String getAutodiscoverContent()
        {
            string content = String.Empty;
            string internalHost = autodiscoverURLs[0].Substring(8);
            string externalHost = autodiscoverURLs[2].Substring(8);

            try
            {
                IPHostEntry intHE = Dns.GetHostEntry(internalHost);
                try
                {
                    Task<HttpResponseMessage> responseT = client.GetAsync(autodiscoverURLs[0]);
                    responseT.Wait();
                    Task<string> contentT = ((HttpResponseMessage)responseT.Result).Content.ReadAsStringAsync();
                    contentT.Wait();
                    content = contentT.Result;
                    Console.WriteLine("got lyncdiscover service from " + autodiscoverURLs[0]);
                    return content;
                }
                catch (Exception)
                {
                    Console.WriteLine("Warning. Cannot access https lyncdiscoverinternal service:" + autodiscoverURLs[0] + ". Trying " + autodiscoverURLs[1] + "...");
                }

                try
                {
                    Task<HttpResponseMessage> responseT = client.GetAsync(autodiscoverURLs[1]);
                    responseT.Wait();
                    Task<string> contentT = ((HttpResponseMessage)responseT.Result).Content.ReadAsStringAsync();
                    contentT.Wait();
                    content = contentT.Result;
                    Console.WriteLine("got lyncdiscover service from " + autodiscoverURLs[1]);
                    return content;
                }
                catch (Exception)
                {
                    Console.WriteLine("Warning. Cannot access http lyncdiscoverinternal service:" + autodiscoverURLs[1] + ". Trying " + autodiscoverURLs[2] + "...");
                }
            }
            catch
            {
                Console.WriteLine("Warning. Cannot access https or http lyncdiscoverinternal service, trying lyncdiscover DNS entry...");
            }

            try
            {
                IPHostEntry extHE = Dns.GetHostEntry(externalHost);
                try
                {
                    Task<HttpResponseMessage> responseT = client.GetAsync(autodiscoverURLs[2]);
                    responseT.Wait();
                    Task<string> contentT = ((HttpResponseMessage)responseT.Result).Content.ReadAsStringAsync();
                    contentT.Wait();
                    content = contentT.Result;
                    Console.WriteLine("got lyncdiscover service from " + autodiscoverURLs[2]);
                    return content;
                }
                catch (Exception)
                {
                    Console.WriteLine("Warning. Cannot access https lyncdiscover service:" + autodiscoverURLs[2] + ". Trying " + autodiscoverURLs[3] + "...");
                }

                try
                {
                    Task<HttpResponseMessage> responseT = client.GetAsync(autodiscoverURLs[3]);
                    responseT.Wait();
                    Task<string> contentT = ((HttpResponseMessage)responseT.Result).Content.ReadAsStringAsync();
                    contentT.Wait();
                    content = contentT.Result;
                    Console.WriteLine("got lyncdiscover service from " + autodiscoverURLs[3]);
                    return content;
                }
                catch (Exception)
                {
                    Console.WriteLine("Error. Cannot access http lyncdiscover service:" + autodiscoverURLs[3] + ". No lyncdiscover service defined");
                }
            }
            catch
            {
                Console.WriteLine("Error. cannot access external https or http lyncdiscover service");
            }
            return content;
        }

        private void createUcwaServiceFQDN(String userHref)
        {
            ucwaServiceFQDN = userHref.Remove(userHref.IndexOf("/Autodiscover/"));
        }

        private String getUserAuthenticationChallenge(string userHRef)
        {
            Task<HttpResponseMessage> nograntT = client.GetAsync(userHRef);
            nograntT.Wait();
            HttpResponseMessage nogrant = (HttpResponseMessage)nograntT.Result;

            string s = null;

            try
            {
                nogrant.EnsureSuccessStatusCode();
            }
            catch (HttpRequestException)
            {
                IEnumerable<String> headers = nogrant.Headers.GetValues("WWW-Authenticate");
                foreach (string header in headers)
                {
                    if (header.StartsWith("MsRtcOAuth"))
                    {
                        int start = header.IndexOf("MsRtcOAuth href=");
                        start = start + 17;
                        s = header.Substring(start);
                        int end = s.IndexOf("\"");
                        s = s.Remove(end);
                    }
                }
            }
            return s;
        }

        private String getAuthToken(String authChallenge)
        {
            StringBuilder authContentSB;
            if (password.Equals(String.Empty))
            {
                authContentSB = new StringBuilder("grant_type=urn:microsoft.rtc:windows");
            }
            else
            {
                authContentSB = new StringBuilder("grant_type=password&username=");
                authContentSB.Append(sipURI).Append("&password=").Append(password);
            }

            HttpContent authContent = new StringContent(authContentSB.ToString());

            authContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
            authContent.Headers.ContentType.CharSet = "UTF-8";

            Task<HttpResponseMessage> grantRespT = client.PostAsync(authChallenge, authContent);
            grantRespT.Wait();
            HttpResponseMessage grantResp = grantRespT.Result;
            grantResp.EnsureSuccessStatusCode();

            Task<string> grantContT = grantResp.Content.ReadAsStringAsync();
            grantContT.Wait();
            string grantCont = grantContT.Result;

            int tstart = grantCont.IndexOf("cwt=");
            string token = grantCont.Substring(tstart);
            int tend = token.IndexOf("\",");
            token = token.Remove(tend);
            token = "Bearer " + token;

            return token;
        }

        private string getApplications(string userHRef, string token)
        {
            client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", token);
            Task<HttpResponseMessage> authorizedUserResponseT = client.GetAsync(userHRef);
            authorizedUserResponseT.Wait();
            HttpResponseMessage authorizedUserResponse = authorizedUserResponseT.Result;

            Task<string> authorizedUserContentT = authorizedUserResponse.Content.ReadAsStringAsync();
            string authorizedUserContent = authorizedUserContentT.Result;
            authorizedUserResponse.EnsureSuccessStatusCode();

            return getContextHref(authorizedUserContent, "applications");
        }
        
        private string createApplicationEndPoint(string applicationsHRef)
        {
            StringBuilder appEndPointStringSB = new StringBuilder("{\"UserAgent\":\"UCWA CallViaPhone\",\"EndpointId\":\"");
            appEndPointStringSB.Append(appGUID).Append("\",\"Culture\":\"en-US\"}");

            HttpContent content = new StringContent(appEndPointStringSB.ToString());
            content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

            Task<HttpResponseMessage> responseT = client.PostAsync(applicationsHRef, content);
            responseT.Wait();
            HttpResponseMessage response = responseT.Result;
            response.EnsureSuccessStatusCode();

            Task<string> contentT = response.Content.ReadAsStringAsync();
            contentT.Wait();
            return contentT.Result;
        }

        public void startCall()
        {
            string communication = createCommunication(getCommunicationHRef());

            Communication commObject = getCommunication(communication);
            string command = "{\"phoneNumber\":\"" + fromPhoneNumber + "\",\"importance\":\"Normal\",\"subject\":\"CvW Test\",\"to\":\"tel:" + toPhoneNumber + "\",\"operationId\":\"" + commObject.getOperationId() + "\"}";
            HttpContent content = new StringContent(command);
            content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

            Task<HttpResponseMessage> responseT = client.PostAsync(commObject.getStartPhoneAudio(), content);
            responseT.Wait();
            HttpResponseMessage response = responseT.Result;
            try
            {
                response.EnsureSuccessStatusCode();
            }
            catch (Exception)
            {
                Console.WriteLine("could not start a call for " + command);
            }
        }

        private string getCommunicationHRef()
        {
            System.Web.Script.Serialization.JavaScriptSerializer serializer = new JavaScriptSerializer();
            Dictionary<string, Object> jasondComm = (Dictionary<string, Object>)serializer.DeserializeObject(appEndPoint);
            Dictionary<string, Object> embeddedD = (Dictionary<string, Object>)jasondComm["_embedded"];
            Dictionary<string, Object> communicationD = (Dictionary<string, Object>)embeddedD["communication"];
            Dictionary<string, Object> linksD = (Dictionary<string, Object>)communicationD["_links"];
            Dictionary<string, Object> linksSelfD = (Dictionary<string, Object>)linksD["self"];
            return (String)linksSelfD["href"];
        }

        private string createCommunication(string communicationHRef)
        {
            communicationHRef = ucwaServiceFQDN + communicationHRef;
            Task<HttpResponseMessage> respT = client.GetAsync(communicationHRef);
            respT.Wait();
            HttpResponseMessage resp = respT.Result;
            Task<string> contentT = resp.Content.ReadAsStringAsync();
            contentT.Wait();
            return contentT.Result;
        }

        private Communication getCommunication(string communication)
        {            
            System.Web.Script.Serialization.JavaScriptSerializer serializer = new JavaScriptSerializer();
            Dictionary<string, Object> jasond = (Dictionary<string, Object>)serializer.DeserializeObject(communication);            
            String operationId = jasond.ElementAt(0).Key;

            Dictionary<string, Object> linksD = (Dictionary<string, Object>)jasond["_links"];
            Dictionary<string, Object> startPhoneAudioD = (Dictionary<string, Object>)linksD["startPhoneAudio"];

            String startPhoneAudioURI = (String)startPhoneAudioD["href"];
            startPhoneAudioURI = ucwaServiceFQDN + startPhoneAudioURI;

            return new Communication(operationId, startPhoneAudioURI);
        }
        
        private string getPhonesHRef()
        {
            System.Web.Script.Serialization.JavaScriptSerializer serializer = new JavaScriptSerializer();
            Dictionary<string, Object> jasondComm = (Dictionary<string, Object>)serializer.DeserializeObject(appEndPoint);
            Dictionary<string, Object> embeddedD = (Dictionary<string, Object>)jasondComm["_embedded"];            
            Dictionary<string, Object> meD = (Dictionary<string, Object>)embeddedD["me"];
            Dictionary<string, Object> meLinksD = (Dictionary<string, Object>)meD["_links"];
            Dictionary<string, Object> phonesD = (Dictionary<string, Object>)meLinksD["phones"];
            return (String)phonesD["href"];
        }

        private String getContextHref(String content, String context)
        {
            System.Web.Script.Serialization.JavaScriptSerializer serializer = new JavaScriptSerializer();
            Dictionary<string, Object> jasond = (Dictionary<string, Object>)serializer.DeserializeObject(content);
            Dictionary<string, Object> linksd = (Dictionary<string, Object>)jasond["_links"];
            Dictionary<string, Object> contextd = (Dictionary<string, Object>)linksd[context];
            return (String)contextd["href"];            
        }
    }
}
